'''
    此处是算法模块
    ========
    算法模块的输入主要由四部分组成:
    -` 投资年限 ` : 投资年限,用于下面的"三三四四"法则对用户基金的筛选
    -` 用户年收入 ` : 用户年收入,用于下方的AHP分析模块计算相应的抗风险能力;
    -` 用户期望取得收益率 ` : 用户期望取得的收益率,用于下方的AHP分析模块计算相应投机心理指标权重;
    -` 用户选择的基金领域 ` : 用户选择的基金领域,用于后续进一步筛选基金


    此模块主要包括两部分内容:
        1. "4433"选基金法则,选出候选范围内的基金;
        2. AHP分析法分析用户输入,得到在用户心目中如下四个指标的权重:
            a. 投机心理指标,越高代表用户越看重基金的盈利能力；
            b. 抗风险能力指标,越高代表用户越看重基金组合的抗风险能力;
            c. 短期指标,占比越大表示用户越看重基金的短期盈利能力;
            d. 长期指标,占比越大表示用户越看重基金的长期盈利能力;
'''

import numpy as np

from BusinessSubSystem.constUtils import VERY_SIGNIFICANTLY_HIGHER_THAN_ANOTHER, SIGNIFICANTLY_HIGHER_THAN_ANOTHER, \
    HIGHER_THAN_ANOTHER, SLIGHRTLY_HIGHER_THAN_ANOTHER, EQUIVILENT_TO_ANOTHER, SLIGHRTLY_LOWER_THAN_ANOTHER, \
    LOWER_THAN_ANOTHER, SIGNIFICANTLY_LOWER_THAN_ANOTHER, VERY_SIGNIFICANTLY_LOWER_THAN_ANOTHER
from BusinessSubSystem.models import fundToFundField, fund
from BusinessSubSystem.notSaveInDataBaseModel import FundListSingleton, UserChoiceSingleton


class AHP:
    """ AHP层次分析法实现类

        注:
            一般使用AHP层次分析法都要进行一致性检测,因为此处使用的判断矩阵是二维的,所以就不会出现用户认为A比B重要,B比C重要,C又比A重要这样的逻辑谬误,也就不需要一致性检验
    """


    def __init__(self, array):
        ## 记录矩阵相关信息
        self.array = array
        ## 记录矩阵大小
        self.n = array.shape[0]
        # 矩阵的特征值和特征向量
        self.eig_val, self.eig_vector = np.linalg.eig(self.array)
        # 矩阵的最大特征值
        self.max_eig_val = np.max(self.eig_val)
        # 矩阵最大特征值对应的特征向量
        self.max_eig_vector = self.eig_vector[:, np.argmax(self.eig_val)].real

    """
        特征值法求权重
    """

    def cal_weight__by_eigenvalue_method(self):
        # 将矩阵最大特征值对应的特征向量进行归一化处理就得到了权重
        array_weight = self.max_eig_vector / np.sum(self.max_eig_vector)
        # 返回权重向量的值
        return array_weight


'''
    “4433"选股法则,下方首先是风险类基金的"4433"选股法则
'''

def fund_selection_rule(year):
    '''
        "4433"选股法则
        :param year: 用户期望的投资年限
        :return: 筛选过的股票型以及混合型基金
    '''
    fund_list_singleton_object = FundListSingleton.get_instance()

    all_fund_list = fundToFundField.objects.all()

    # 如果基金数不足十个直接返回
    if len(all_fund_list) < 10:
        fund_list_singleton_object.selected_risk_fund_list_for_one_or_two_years = all_fund_list
        fund_list_singleton_object.selected_risk_fund_list_for_three_years = all_fund_list
        fund_list_singleton_object.selected_risk_fund_list_for_four_years = all_fund_list

    else:
        if year == '1' or year == '2':
            fund_list_singleton_object.selected_risk_fund_list_for_one_or_two_years = fund_selection_rule_for_one_or_two(all_fund_list)
        elif year == '3':
            fund_list_singleton_object.selected_risk_fund_list_for_three_years = fund_selection_rule_for_three(all_fund_list)
        elif year == '4':
            fund_list_singleton_object.selected_risk_fund_list_for_four_years = fund_selection_rule_for_four(all_fund_list)
        else:
            pass

def fund_selection_rule_for_one_or_two(fund_list):
    '''
         这是针对短期基金的"4433"选股法则
         此处"4433"法则是指:当基金的本月和三个月收益排名在前1/4且基金的六个月收益和一年内收益排名在前1/3
        :param fund_list:基金领域与基金联系类列表
        :return:筛选后的基金领域与基金联系类列表
    '''
    fund_selected_list = []
    for each_fund in fund_list:
        srank_l1m = each_fund.fund_id.srank_l1m.split("/")
        srank_l3m = each_fund.fund_id.srank_l3m.split("/")
        srank_l6m = each_fund.fund_id.srank_l6m.split("/")
        srank_l1y = each_fund.fund_id.srank_l1y.split("/")
        if srank_l1m[0] != "" and srank_l3m[0] != "" and srank_l6m[0] != "" and srank_l1y[0] != "":
            try:
                if (int(srank_l1m[0])/int(srank_l1m[1]) < 1/4) and (int(srank_l3m[0])/int(srank_l3m[1]) < 1/4) and (int(srank_l6m[0])/int(srank_l6m[1]) < 1/3) and (int(srank_l1y[0])/int(srank_l1y[1]) < 1/3):
                    fund_selected_list.append(each_fund)
            except Exception as e:
                print(e)
    return fund_selected_list

def fund_selection_rule_for_three(fund_list):
    '''
        这是针对三年期基金的"4433"选股法则
        这里的"4433"法则:当基金的三个月和六个月收益排名在前1/4且基金的一年收益和三年收益排名在前1/3
        :param fund_list:基金领域与基金联系类列表
        :return:筛选后的基金领域与基金联系类列表
    '''
    fund_selected_list = []
    for each_fund in fund_list:
        srank_l3m = each_fund.fund_id.srank_l3m.split("/")
        srank_l6m = each_fund.fund_id.srank_l6m.split("/")
        srank_l1y = each_fund.fund_id.srank_l1y.split("/")
        srank_l3y = each_fund.fund_id.srank_l3y.split("/")
        if srank_l3y[0] != "" and srank_l3m[0] != "" and srank_l6m[0] != "" and srank_l1y[0] != "":
            try:
                if (int(srank_l3y[0])/int(srank_l3y[1]) < 1/3) and (int(srank_l3m[0])/int(srank_l3m[1]) < 1/4) and (int(srank_l6m[0])/int(srank_l6m[1]) < 1/4) and (int(srank_l1y[0])/int(srank_l1y[1]) < 1/3):
                    fund_selected_list.append(each_fund)
            except Exception as e:
                print(e)
    return fund_selected_list

def fund_selection_rule_for_four(fund_list):
    '''
        这是针对四年基金的"4433"选股法则
        这里的"4433"法则:当基金的六个月和一年收益排名在前1/4且基金的三年收益和自成立以来收益排名在前1/3
        :param fund_list:基金领域与基金联系类列表
        :return:筛选后的基金领域与基金联系类列表
    '''
    fund_selected_list = []
    for each_fund in fund_list:
        srank_l6m = each_fund.fund_id.srank_l6m.split("/")
        srank_l1y = each_fund.fund_id.srank_l1y.split("/")
        srank_l3y = each_fund.fund_id.srank_l3y.split("/")
        srank_base = each_fund.fund_id.srank_base.split("/")
        if srank_l3y[0] != "" and srank_base[0] != "" and srank_base[0] != "" and srank_base[0] != "":
            try:
                if (int(srank_l3y[0])/int(srank_l3y[1]) < 1/3) and (int(srank_base[0])/int(srank_base[1]) < 1/3) and (int(srank_l6m[0])/int(srank_l6m[1]) < 1/4) and (int(srank_l1y[0])/int(srank_l1y[1]) < 1/4):
                    fund_selected_list.append(each_fund)
            except Exception as e:
                print(e)
    return fund_selected_list

'''
    下方是固收类基金的"4433"法则
'''

def bond_fund_selection_rules(year):
    '''
    "4433"选股法则
    :param year: 用户期望的投资年限
    :return: 筛选过的证券型基金
    '''
    all_fund_list = fund.objects.filter(fund_type="bond_fund").all()

    fund_list_singleton_object = FundListSingleton.get_instance()

    # 如果基金数不足十个直接返回
    if len(all_fund_list) < 10:
        fund_list_singleton_object.selected_bond_fund_list_for_one_or_two_years = all_fund_list
        fund_list_singleton_object.selected_bond_fund_list_for_three_years = all_fund_list
        fund_list_singleton_object.selected_bond_fund_list_for_four_years = all_fund_list

    else:
        if year == '1' or year == '2':
            fund_list_singleton_object.selected_bond_fund_list_for_one_or_two_years = bond_fund_selection_rules_for_one_or_two(
                all_fund_list)
        elif year == '3':
            fund_list_singleton_object.selected_bond_fund_list_for_three_years = bond_fund_selection_rules_for_three(
                all_fund_list)
        elif year == '4':
            fund_list_singleton_object.selected_bond_fund_list_for_four_years = bond_fund_selection_rules_for_four(
                all_fund_list)
        else:
            pass

def bond_fund_selection_rules_for_one_or_two(fund_list):
    '''
     这是针对证券类短期基金的"4433"选股法则
     此处"4433"法则是指:当基金的本月和三个月收益排名在前1/4且基金的六个月收益和一年内收益排名在前1/3
    :param fund_list:基金领域与基金联系类列表
    :return:筛选后的基金领域与基金联系类列表
    '''
    fund_selected_list = []
    for each_fund in fund_list:
        srank_l1m = each_fund.srank_l1m.split("/")
        srank_l3m = each_fund.srank_l3m.split("/")
        srank_l6m = each_fund.srank_l6m.split("/")
        srank_l1y = each_fund.srank_l1y.split("/")
        if srank_l1m[0] != "" and srank_l3m[0] != "" and srank_l6m[0] != "" and srank_l1y[0] != "":
            try:
                if (int(srank_l1m[0])/int(srank_l1m[1]) < 1/4) and (int(srank_l3m[0])/int(srank_l3m[1]) < 1/4) and (int(srank_l6m[0])/int(srank_l6m[1]) < 1/3) and (int(srank_l1y[0])/int(srank_l1y[1]) < 1/3):
                    fund_selected_list.append(each_fund)
            except Exception as e:
                print(e)
    return fund_selected_list

def bond_fund_selection_rules_for_three(fund_list):
    '''
    这是针对三年期债券基金的"4433"选股法则
    这里的"4433"法则:当基金的三个月和六个月收益排名在前1/4且基金的一年收益和三年收益排名在前1/3
    :param fund_list:基金领域与基金联系类列表
    :return:筛选后的基金领域与基金联系类列表
    '''
    fund_selected_list = []
    for each_fund in fund_list:
        srank_l3m = each_fund.srank_l3m.split("/")
        srank_l6m = each_fund.srank_l6m.split("/")
        srank_l1y = each_fund.srank_l1y.split("/")
        srank_l3y = each_fund.srank_l3y.split("/")
        if srank_l3y[0] != "" and srank_l3m[0] != "" and srank_l6m[0] != "" and srank_l1y[0] != "":
            try:
                if (int(srank_l3y[0])/int(srank_l3y[1]) < 1/3) and (int(srank_l3m[0])/int(srank_l3m[1]) < 1/4) and (int(srank_l6m[0])/int(srank_l6m[1]) < 1/4) and (int(srank_l1y[0])/int(srank_l1y[1]) < 1/3):
                    fund_selected_list.append(each_fund)
            except Exception as e:
                print(e)
    return fund_selected_list

def bond_fund_selection_rules_for_four(fund_list):
    '''
    这是针对四年期债券基金的"4433"选股法则
    这里的"4433"法则:当基金的六个月和一年收益排名在前1/4且基金的三年收益和自成立以来收益排名在前1/3
    :param fund_list:基金领域与基金联系类列表
    :return:筛选后的基金领域与基金联系类列表
    '''
    fund_selected_list = []
    for each_fund in fund_list:
        srank_l6m = each_fund.srank_l6m.split("/")
        srank_l1y = each_fund.srank_l1y.split("/")
        srank_l3y = each_fund.srank_l3y.split("/")
        srank_base = each_fund.srank_base.split("/")
        if srank_l3y[0] != "" and srank_base[0] != "" and srank_base[0] != "" and srank_base[0] != "":
            try:
                if (int(srank_l3y[0])/int(srank_l3y[1]) < 1/3) and (int(srank_base[0])/int(srank_base[1]) < 1/3) and (int(srank_l6m[0])/int(srank_l6m[1]) < 1/4) and (int(srank_l1y[0])/int(srank_l1y[1]) < 1/4):
                    fund_selected_list.append(each_fund)
            except Exception as e:
                print(e)
    return fund_selected_list

def get_weight_by_ahp(user_id):
    '''
    此处用于得到上面提及的对应的四个指标，也即:
    1. 投机心理指标,越高代表用户越看重基金的盈利能力；
    2. 抗风险能力指标,越高代表用户越看重基金组合的抗风险能力;
    3. 短期指标,占比越大表示用户越看重基金的短期盈利能力;
    4. 长期指标,占比越大表示用户越看重基金的长期盈利能力;
    :param user_id: 用户id
    :return: 用户各选择的权重


    为了方便对照,在下方给出如下矩阵说明:
    财富自由的四个选项：
        1. 0-1 万
        2. 1-10 万
        3. 10-30万
        4. 30万以上

        长期收益预期：
        a. 0.3%
        b. 2.5%
        c. 6%
        d. 10%
        e. 20%

        权重计算

        AHP法中主要是使用专家打分法计算每一项的权重，

        以下为构成输入判断矩阵的表格:

        | 方案编号 | 抗风险能力和获利能力重要性比较      |   与上方选项对应方案序号   |
        | 方案1   | 抗风险能力十分显著高于获利能力(9)   | 1a |    |    |    |    |
        | 方案2   | 抗风险能力显著高于获利能力(7)      | 2a | 1b |    |    |    |
        | 方案3   | 抗风险能力一般高于获利能力(5)      | 3a | 2b |    |    |    |
        | 方案4   | 抗风险能力略微高于获利能力(3)      | 4a | 3b | 1c |    |    |
        | 方案5   | 抗风险能力重要性等同于获利能力(1)   |    | 4b | 2c | 1d |    |
        | 方案6   | 抗风险能力略微低于获利能力(1/3)    |    |    | 3c | 2d | 1e |
        | 方案7   | 抗风险能力一般低于获利能力(1/5)    |    |    | 4c | 3d | 2e |
        | 方案8   | 抗风险能力显著低于获利能力(1/7)    |    |    |    | 4d | 3e |
        | 方案9   | 抗风险能力十分显著低于获利能力(1/9) |    |    |    |    | 4e |


    '''
    user_choice_singleton = UserChoiceSingleton.get_instance()
    user_choice = user_choice_singleton.get_user_choice(user_id)

    # 给出各方案对应的输入矩阵
    programme_one = np.array([[1, 1 / VERY_SIGNIFICANTLY_HIGHER_THAN_ANOTHER], [VERY_SIGNIFICANTLY_HIGHER_THAN_ANOTHER, 1]])
    programme_two = np.array([[1, 1 / SIGNIFICANTLY_HIGHER_THAN_ANOTHER], [SIGNIFICANTLY_HIGHER_THAN_ANOTHER, 1]])
    programme_three = np.array([[1, 1 / HIGHER_THAN_ANOTHER], [HIGHER_THAN_ANOTHER, 1]])
    programme_four = np.array([[1, 1 / SLIGHRTLY_HIGHER_THAN_ANOTHER], [SLIGHRTLY_HIGHER_THAN_ANOTHER, 1]])
    programme_five = np.array([[1, 1 / EQUIVILENT_TO_ANOTHER], [EQUIVILENT_TO_ANOTHER, 1]])
    programme_six = np.array([[1, 1 / SLIGHRTLY_LOWER_THAN_ANOTHER], [SLIGHRTLY_LOWER_THAN_ANOTHER, 1]])
    programme_seven = np.array([[1, 1 / LOWER_THAN_ANOTHER], [LOWER_THAN_ANOTHER, 1]])
    programme_eight = np.array([[1, 1 / SIGNIFICANTLY_LOWER_THAN_ANOTHER], [SIGNIFICANTLY_LOWER_THAN_ANOTHER, 1]])
    programme_nine = np.array([[1, 1 / VERY_SIGNIFICANTLY_LOWER_THAN_ANOTHER], [VERY_SIGNIFICANTLY_LOWER_THAN_ANOTHER, 1]])

    # python没有switch,使用字典代替
    array_risk_choice = {
        "0.003":{
            "1":programme_one,
            "10":programme_two,
            "30":programme_three,
            "99":programme_four
        },
        "0.025":{
            "1":programme_two,
            "10":programme_three,
            "30":programme_four,
            "99":programme_five
        },
        "0.06": {
            "1": programme_four,
            "10": programme_five,
            "30": programme_six,
            "99": programme_seven
        },
        "0.10": {
            "1": programme_five,
            "10": programme_six,
            "30": programme_seven,
            "99": programme_eight
        },
        "0.20": {
            "1": programme_six,
            "10": programme_seven,
            "30": programme_eight,
            "99": programme_nine
        }
    }

    # 特征值法求权重，得到权重后第一部分是投机权重,第二部分是抗风险因子权重
    weight_risk_and_speculation = AHP(array_risk_choice[user_choice["user_expected_return"]][user_choice["user_annual_income"]]).cal_weight__by_eigenvalue_method()

    '''
            下方是根据年限得到的权重:
            选择年限：
            1. 0-1年
            2. 1-2年
            3. 2-3年
            4. 3-4年

            以下为构成输入判断矩阵的表格:
            | 年限编号 | 长期获利重要性和短期获利重要性比较         |   选择年限   |
            | 年限1   | 长期获利重要性显著高于短期获利重要性(7)     |    3-4年    |
            | 年限2   | 长期获利重要性一般高于短期获利重要性(3)     |    2-3年    |
            | 年限3   | 长期获利重要性一般低于短期获利重要性(1/3)   |    1-2年    |
            | 年限4   | 长期获利重要性显著低于短期获利重要性(1/7)   |    0-1年    |
    '''

    programme_year_one = np.array([[1, 1 / SIGNIFICANTLY_HIGHER_THAN_ANOTHER], [SIGNIFICANTLY_HIGHER_THAN_ANOTHER, 1]])
    programme_year_two = np.array([[1, 1 / SLIGHRTLY_HIGHER_THAN_ANOTHER], [SLIGHRTLY_HIGHER_THAN_ANOTHER, 1]])
    programme_year_three = np.array([[1, 1 / SLIGHRTLY_LOWER_THAN_ANOTHER], [SLIGHRTLY_LOWER_THAN_ANOTHER, 1]])
    programme_year_four = np.array([[1, 1 / SIGNIFICANTLY_LOWER_THAN_ANOTHER], [SIGNIFICANTLY_LOWER_THAN_ANOTHER, 1]])

    array_year_choice = {
        "1":programme_year_one,
        "2":programme_year_two,
        "3":programme_year_three,
        "4":programme_year_four
    }

    # 特征值法求权重，得到权重后第一部分是长期权重,第二部分是短期权重
    weight_short_and_long_term = AHP(array_year_choice[user_choice["user_expected_time"]]).cal_weight__by_eigenvalue_method()

    # 四个部分,按顺序分别是投机,抗风险,长期,短期
    return weight_risk_and_speculation.tolist() + weight_short_and_long_term.tolist()

def get_fund_sorted_result(fund_list,weight):
    '''
    得到基金通过多因子模型得到分数排序后结果:

    公式如下:
        基金总成绩 = 短期因子 * (基金的一年风险收益比 * 投机因子 + 基金的一年抗风险波动 * 抗风险因子) + 长期因子 * (基金的三年风险收益比 * 投机因子 + 基金的三年抗风险波动 * 抗风险因子)

    :param fund_list:基金列表
    :param weight:权重列表
    :return:排序后的基金列表
    '''

    list_waiting_for_sorted = []

    for each_fund in fund_list:
        try:
            investment_cost_performance_1y = float(each_fund.investment_cost_performance_1y.strip('%'))
            risk_control_1y = float(each_fund.risk_control_1y.strip('%'))
            investment_cost_performance_3y = float(each_fund.investment_cost_performance_3y.strip('%'))
            risk_control_3y = float(each_fund.risk_control_3y.strip('%'))
        except ValueError:
            continue

        # 计算公式
        total_score = weight[3] * (investment_cost_performance_1y * weight[0] + risk_control_1y * weight[1]) + weight[2] * (investment_cost_performance_3y * weight[0] + risk_control_3y * weight[1])
        scored_fund_obj = {
            "fund":each_fund,
            "fund_score":total_score
        }
        list_waiting_for_sorted.append(scored_fund_obj)
    bubble_sort_fund(list_waiting_for_sorted)
    return list_waiting_for_sorted


def bubble_sort_fund(arr):
    '''
    对经过多因子模型打过分的基金序列使用冒泡算法进行排序
    :param arr: 得到相关权重的矩阵
    :return: 排序后的结果
    '''
    n = len(arr)
    # 遍历所有数组元素
    for i in range(n):
        for j in range(0, n - i - 1):
            if arr[j]["fund_score"] < arr[j + 1]["fund_score"]:
                arr[j], arr[j + 1] = arr[j + 1], arr[j]